Skip to content

Conversation

@Chiman2937
Copy link
Member

@Chiman2937 Chiman2937 commented Dec 21, 2025

📝 변경 사항

마이페이지에서 프로필 수정 Form의 유효성검증 기능을 추가했습니다.

💡 1. 이미지

이미지를 선택할 때 validation이 실행되며 검증을 통과하지 못하면 이미지가 교체되지 않습니다.

이미지 확장자 검증

image

이미지 크기 검증

image

이미지 높이, 너비 검증

image

💡 2. mbti

MBTI 작성중(onChange)

  • MBTI 작성 중이면서 string이 유효한 MBTI의 일부라면 에러메시지가 나타나지 않습니다.(ex. IS)
image



  • 대소문자를 모두 허용합니다.
image



  • 유효하지 않은 MBTI 작성 시
image

MBTI 작성 완료

  • onBlur 시점에 유효한 MBTI인지 검증합니다. 4글자가 아니거나 유효한 MBTI가 아니라면 에러메시지가 나타납니다.
image

onSubmit 시

mbti는 대문자로 변환됩니다.


🔗 관련 이슈

Closes #211


🧪 테스트 방법

  • 수동 테스트 검증(로컬 환경)
  • 유닛 테스트 검증
  • 통합 테스트 검증

📸 스크린샷 (선택)


📋 체크리스트

  • 관련 문서를 업데이트했습니다 (필요한 경우)
  • 테스트를 추가/수정했습니다 (필요한 경우)
  • Breaking change가 있다면 명시했습니다

💬 추가 코멘트


CodeRabbit Review는 자동으로 실행되지 않습니다.

Review를 실행하려면 comment에 아래와 같이 작성해주세요

@coderabbitai review

Summary by CodeRabbit

  • 새로운 기능

    • 프로필 편집: 필드별 실시간 유효성 표시(힌트 메시지) 및 MBTI 대문자 정규화
    • 이미지 업로드: 중앙 설정(크기/가로세로/형식/확장자) 기반 클라이언트 검증 및 검증 함수 추가
    • 닉네임: 중복 확인 검사 및 제출 전 검증
  • 개선

    • 제출 흐름 개선: 변경된 필드만 전송, 제출 버튼 상태가 폼 유효성에 따라 동적 변경
  • 버그 수정

    • 입력 필드의 required 속성 제거로 검증 흐름 일원화

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 21, 2025

Walkthrough

프로필 편집 폼에 필드별 Hint 오류 표시를 추가하고, zod 기반 유효성 스키마와 이미지 검증 유틸을 도입했으며, 닉네임 중복 확인 및 MBTI 대문자 정규화와 제출 흐름(조건별 업데이트 및 통합 오류 처리)을 적용했습니다.

Changes

코호트 / 파일(s) 변경 요약
프로필 필드 - 오류 Hint 추가
src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx, src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx, src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx
UI의 Hint 컴포넌트 임포트, Label/input id 정리(필요 속성 제거), isInvalid 계산 추가, 유효하지 않을 때 첫 번째 오류 메시지를 Hint로 조건부 렌더링.
프로필 편집 모달 - 제출/검증 리팩터링
src/components/pages/user/profile/profile-edit-modal/index.tsx
zod 스키마 및 API 임포트, 필드별 onChange/onBlur 유효성 검사 연결, 닉네임 중복 조회(onSubmitAsync) 추가, MBTI 대문자 정규화, 변경된 필드만 전송하도록 submit 흐름 재구성, 통합 오류 처리 및 동적 제출 버튼 상태(form.Subscribe) 적용.
이미지 입력 - 클라이언트 검증 적용
src/components/ui/imageinput/index.tsx, src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx
IMAGE_CONFIG 기반 accept 기본값 사용, 파일 선택 시 validateImage로 비동기 검증 수행; 검증 실패 시 alert 및 입력 초기화, 유효 파일만 처리. (image-field에서는 기존 accept='image/*' 제거)
이미지 상수 추가
src/lib/constants/image.ts
IMAGE_CONFIG 새로 추가: maxSizeBytes(20MB), maxWidth(2000), maxHeight(2000), allowedTypes(['image/jpeg','image/png','image/webp']), allowedExtensions(['.jpg','.jpeg','.png','.webp']).
이미지 검증 유틸
src/lib/validateImage.ts
validateImage(file) 추가: 확장자, 파일 크기, 이미지 가로/세로 픽셀 검사 순으로 검증하고 한국어 오류 메시지 반환. 내부에 이미지 치수 획득 헬퍼 포함.
마이페이지 검증 스키마
src/lib/schema/mypage.ts
zod 기반 스키마 5종 추가: profileImageOnChangeSchema, nickNameOnChangeSchema(2~20자, 한글 메시지), profileMessageOnChangeSchema(최대20자), mbtiOnChangeSchema(선택적 4자 패턴 검증), mbtiOnBlurSchema(정확히 4자 패턴 검사).

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Form as 프로필 편집 폼
    participant Validator as zod 검증기 (lib/schema/mypage)
    participant API as 서버 (닉네임 확인 / 업데이트)
    participant ImageUtil as validateImage
    participant Modal as 모달 UI

    User->>Form: 필드 입력 (onChange)
    Form->>Validator: onChange 스키마 검증
    Validator-->>Form: 필드 유효성 결과
    Form->>Modal: isInvalid 상태 반영 -> Hint 렌더링

    User->>Form: MBTI 입력 후 onBlur
    Form->>Validator: onBlur 스키마 검증 (4자 엄격)
    Validator-->>Form: 검증 결과

    User->>Form: 이미지 파일 선택
    Form->>ImageUtil: validateImage(file)
    ImageUtil-->>Form: 검증 성공/실패
    alt 실패
        Form->>Modal: alert 및 입력 초기화
    else 성공
        Form->>Form: 이미지 임시 반영
    end

    User->>Form: 제출 클릭
    Form->>API: 닉네임 중복 확인
    API-->>Form: 중복 여부
    alt 닉네임 중복
        Form->>Modal: 폼 수준 오류 바인딩 (nickName) 표시
    else 사용 가능
        Form->>API: 변경된 필드만 포함해 updateUser 호출
        Form->>API: (변경 시) updateUserImage 호출
        API-->>Form: 성공/실패
        Form->>Modal: 성공/오류 알림 처리
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • 집중 검토 필요 항목:
    • src/components/pages/user/profile/profile-edit-modal/index.tsx — 제출 흐름, 닉네임 중복 처리 및 에러 바인딩 로직
    • src/lib/schema/mypage.ts — MBTI 정규식/refine 로직과 한글 에러 메시지 일관성
    • src/lib/validateImage.tssrc/components/ui/imageinput/index.tsx — 파일 검증 순서, 리소스(객체 URL) 해제 및 사용자 경험(알림) 처리

Possibly related PRs

Suggested reviewers

  • wooktori

Poem

🐰 풀밭에서 폼을 다듬었네,
에러는 Hint로 살며시 말하고,
MBTI는 대문자로 폴짝 뛰어,
이미지도 검사받고 깨끗히 들어가네.
축하해요 — 토끼가 방긋! 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 마이페이지 프로필 수정 폼 유효성 검사 추가라는 변경사항의 주요 내용을 명확하게 설명하고 있습니다.
Description check ✅ Passed PR 설명이 변경 사항, 관련 이슈, 테스트 방법, 체크리스트 등 필수 섹션을 갖추고 있으며 스크린샷으로 상세히 설명하고 있습니다.
Linked Issues check ✅ Passed PR 변경사항이 이슈 #211의 요구사항(MBTI 16가지 조합 검증, submit 시 대문자 변환, 이미지 검증)을 모두 충족합니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 마이페이지 프로필 수정 폼 유효성 검사 목표에 정렬되어 있으며 범위를 벗어나는 변경이 없습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chiyoung-feat/mypage-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Dec 21, 2025

🎭 Playwright Report

E2E Test가 성공적으로 완료되었습니다.

Test 요약 내용을 확인해주세요.

Status Build Log Updated (UTC)
✅ Ready View Build 2025-12-21 05:24:26

📊 Test Summary

  • ✅ Passed: 9
  • ❌ Failed: 0
  • ⏱️ Duration: 46.6s

📜 Test Details

✅ Passed Tests (9)
  • profile.test.ts (9)
    • [chromium] 비로그인 상태에서 /mypage 접속 시 /login으로 redirect 되는 지 테스트
    • [chromium] 나의 프로필 페이지로 접속 시 /mypage로 /redirect 되는 지 테스트
    • [chromium] 존재하지 않는 프로필 페이지로 접속 시 404 redirect 되는 지 테스트
    • [firefox] 비로그인 상태에서 /mypage 접속 시 /login으로 redirect 되는 지 테스트
    • [firefox] 나의 프로필 페이지로 접속 시 /mypage로 /redirect 되는 지 테스트
    • [firefox] 존재하지 않는 프로필 페이지로 접속 시 404 redirect 되는 지 테스트
    • [webkit] 비로그인 상태에서 /mypage 접속 시 /login으로 redirect 되는 지 테스트
    • [webkit] 나의 프로필 페이지로 접속 시 /mypage로 /redirect 되는 지 테스트
    • [webkit] 존재하지 않는 프로필 페이지로 접속 시 404 redirect 되는 지 테스트

@github-actions github-actions bot requested a review from wooktori December 21, 2025 04:53
@github-actions
Copy link

github-actions bot commented Dec 21, 2025

🎨 Storybook Report

변경 사항이 없습니다

모든 Story가 이전 빌드와 동일합니다.

Status Storybook Build Log Updated (UTC)
✅ Unchanged View Storybook View Build 2025-12-21 05:24:16

@github-actions
Copy link

github-actions bot commented Dec 21, 2025

📊 Coverage Report

Status Build Log Updated (UTC)
✅ Ready View Build 2025-12-21 05:22:54

📉 #216main에 병합하면 coverage가 0.05% 감소합니다.

Coverage 요약

@@             Coverage Diff             @@
##             main     #216       +/-   ##
===========================================
- Coverage   33.66%   33.61%    -0.05%     
===========================================
  Files         155      158        +3     
  Lines        6555     6712      +157     
  Branches      250      250         0     
===========================================
+ Hits         2207     2256       +49     
+ Misses       4348     4456      +108     

영향받은 파일

파일 Coverage 변화
/home/runner/work/WeGo_FrontEnd/WeGo_FrontEnd/src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx 22.22% (+0.41%) ⬆️
/home/runner/work/WeGo_FrontEnd/WeGo_FrontEnd/src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx 24.32% (-10.29%) ⬇️
/home/runner/work/WeGo_FrontEnd/WeGo_FrontEnd/src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx 31.03% (-3.58%) ⬇️
/home/runner/work/WeGo_FrontEnd/WeGo_FrontEnd/src/components/pages/user/profile/profile-edit-modal/index.tsx 21.42% (+1.42%) ⬆️
/home/runner/work/WeGo_FrontEnd/WeGo_FrontEnd/src/components/ui/imageinput/index.tsx 15.54% (+0.51%) ⬆️
/home/runner/work/WeGo_FrontEnd/WeGo_FrontEnd/src/lib/validateImage.ts 8.92% (+8.92%) ⬆️
/home/runner/work/WeGo_FrontEnd/WeGo_FrontEnd/src/lib/constants/image.ts 100.00% (+100.00%) ⬆️
/home/runner/work/WeGo_FrontEnd/WeGo_FrontEnd/src/lib/schema/mypage.ts 72.22% (+72.22%) ⬆️

@github-actions
Copy link

github-actions bot commented Dec 21, 2025

🚀 PR Preview Report

Build가 성공적으로 완료되었습니다.

Preview에서 변경사항을 확인하세요.

Status Preview Build Log Updated (UTC)
✅ Ready Visit Preview View Logs 2025-12-21 05:24:05

@Chiman2937 Chiman2937 added the Ready For Review! 리뷰 받을 준비가 되었습니다. label Dec 21, 2025
@Chiman2937
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx (1)

10-10: isTouched 체크 누락으로 인한 UX 이슈

isInvalid 조건에 isTouched 체크가 없어서, 사용자가 필드와 상호작용하기 전에도 에러 메시지가 표시될 수 있습니다. 다른 필드 컴포넌트들과 일관성을 위해 isTouched 체크를 추가하는 것이 좋습니다.

🔎 권장 수정안
-  const isInvalid = !field.state.meta.isValid;
+  const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;

Based on learnings, TanStack Form 필드에서는 isTouched && !isValid 패턴을 사용하는 것이 권장됩니다.

src/lib/validateImage.ts (1)

4-11: 에러 메시지에서 최대 크기를 동적으로 계산하는 것을 권장합니다.

현재 에러 메시지에 "최대 20MB"가 하드코딩되어 있습니다. IMAGE_CONFIG.maxSizeBytes 값이 변경될 경우 에러 메시지와 불일치가 발생할 수 있습니다.

🔎 동적 최대 크기 계산 제안
  // 1. 파일 크기 검증
  if (file.size > IMAGE_CONFIG.maxSizeBytes) {
    const currentSizeMB = (file.size / (1024 * 1024)).toFixed(0);
+   const maxSizeMB = (IMAGE_CONFIG.maxSizeBytes / (1024 * 1024)).toFixed(0);
    return {
      valid: false,
-     error: `이미지 크기가 너무 큽니다. 최대 20MB까지 가능합니다. \n(현재: ${currentSizeMB}MB)`,
+     error: `이미지 크기가 너무 큽니다. 최대 ${maxSizeMB}MB까지 가능합니다. \n(현재: ${currentSizeMB}MB)`,
    };
  }
src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx (1)

15-32: 접근성 향상을 위해 Input에 id 추가를 권장합니다.

Label의 htmlFor와 매칭되는 id 속성을 Input에 추가하면 스크린 리더 지원이 개선됩니다.

🔎 제안된 수정 사항
       <Input
+        id='profile-mbti'
         className='bg-mono-white focus:border-mint-500 rounded-2xl border border-gray-300'
         maxLength={4}
         placeholder='MBTI를 입력해주세요'
         type='text'
         value={field.state.value}
         onBlur={field.handleBlur}
         onChange={(e) => {
           field.handleChange(e.target.value);
           field.setMeta((prev) => ({
             ...prev,
             errorMap: {
               ...prev.errorMap,
               onBlur: undefined,
             },
           }));
         }}
       />
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcdf677 and ba30ecc.

📒 Files selected for processing (8)
  • src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx (1 hunks)
  • src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx (1 hunks)
  • src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx (2 hunks)
  • src/components/pages/user/profile/profile-edit-modal/index.tsx (6 hunks)
  • src/components/ui/imageinput/index.tsx (2 hunks)
  • src/lib/constants/image.ts (1 hunks)
  • src/lib/schema/mypage.ts (1 hunks)
  • src/lib/validateImage.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-17T09:40:37.733Z
Learnt from: HopeFullee
Repo: WeGo-Together/WeGo_FrontEnd PR: 205
File: src/components/pages/post-meetup/fields/cap-field/index.tsx:47-47
Timestamp: 2025-12-17T09:40:37.733Z
Learning: In TanStack Form with Zod validation, when field.state.meta.isValid is false, field.state.meta.errors is guaranteed to have at least one entry. Therefore, in an isInvalid check defined as field.state.meta.isTouched && !field.state.meta.isValid, safely access field.state.meta.errors[0] without extra null/empty checks. This guideline applies to TSX form fields under src/components/pages (and similar forms using the same pattern).

Applied to files:

  • src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx
  • src/components/pages/user/profile/profile-edit-modal/index.tsx
  • src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx
  • src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx
🧬 Code graph analysis (3)
src/components/pages/user/profile/profile-edit-modal/index.tsx (6)
src/hooks/use-user/use-user-update/index.ts (1)
  • useUpdateUser (7-17)
src/hooks/use-user/use-user-image-update/index.ts (1)
  • useUserImageUpdate (7-17)
src/api/index.ts (1)
  • API (22-22)
src/types/service/user.ts (1)
  • UpdateMyInfoPayloads (21-25)
src/lib/schema/mypage.ts (5)
  • profileImageOnChangeSchema (3-7)
  • nickNameOnChangeSchema (9-12)
  • profileMessageOnChangeSchema (14-16)
  • mbtiOnChangeSchema (18-28)
  • mbtiOnBlurSchema (30-36)
src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx (1)
  • ImageField (12-55)
src/lib/validateImage.ts (1)
src/lib/constants/image.ts (1)
  • IMAGE_CONFIG (1-7)
src/components/ui/imageinput/index.tsx (1)
src/lib/validateImage.ts (1)
  • validateImage (3-34)
🪛 Biome (2.1.2)
src/components/pages/user/profile/profile-edit-modal/index.tsx

[error] 125-125: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)


[error] 129-129: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)


[error] 134-134: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)


[error] 139-139: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)

🔇 Additional comments (9)
src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx (1)

10-10: 유효성 검사 패턴이 올바르게 적용되었습니다.

isInvalid 계산과 Hint 조건부 렌더링이 프로젝트의 확립된 패턴을 따르고 있으며, isInvalidtrue일 때 errors[0]이 보장되므로 안전하게 접근할 수 있습니다.

Based on learnings, this pattern is safe and consistent with other form fields in the codebase.

Also applies to: 22-22

src/lib/schema/mypage.ts (1)

18-35: MBTI 검증 스키마 구현이 적절함

mbtiOnChangeSchema는 입력 중 부분 검증을, mbtiOnBlurSchema는 완료 시 전체 검증을 수행하여 요구사항에 맞게 구현되었습니다. 대소문자 모두 허용하고 빈 값도 유효하게 처리합니다.

src/components/pages/user/profile/profile-edit-modal/index.tsx (1)

113-132: children prop 사용은 TanStack Form의 권장 패턴

Biome에서 children prop 사용에 대한 경고가 있지만, 이는 @tanstack/react-formform.Field 컴포넌트에서 render props 패턴으로 권장되는 사용법입니다. 이 경고는 무시해도 됩니다.

src/components/ui/imageinput/index.tsx (1)

109-117: 이미지 검증 로직이 적절하게 구현됨

파일 선택 시 검증을 수행하고, 실패 시 즉시 사용자에게 피드백을 제공합니다. maxFiles=1인 프로필 이미지 사용 케이스에 적합합니다.

alert 대신 toast로 변경하는 TODO가 있는데, 일관된 UX를 위해 추후 개선이 필요합니다.

src/lib/validateImage.ts (2)

1-3: LGTM!

import 구문과 함수 시그니처가 적절합니다. 반환 타입 { valid: boolean; error?: string }이 유효성 검사 결과를 명확하게 표현합니다.


22-34: 확장자 검증 로직이 정확합니다.

대소문자 구분 없이 확장자를 검증하는 로직이 올바르게 구현되었습니다.

다만 IMAGE_CONFIG에 정의된 maxWidthmaxHeight는 현재 이 함수에서뿐만 아니라 전체 코드베이스에서 사용되지 않고 있습니다. 이미지 크기 검증이 필요한 경우 구현이 필요하고, 불필요하다면 설정에서 제거하는 것을 고려해 주세요.

Likely an incorrect or invalid review comment.

src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx (3)

3-3: LGTM! 에러 표시 패턴이 올바릅니다.

Hint 컴포넌트 import 및 조건부 에러 표시 로직이 TanStack Form 패턴을 올바르게 따르고 있습니다. Learning에 따르면 isValid가 false일 때 errors[0]는 보장되므로 안전하게 접근할 수 있습니다.

Also applies to: 10-10, 33-33


17-17: LGTM! maxLength 속성이 적절합니다.

MBTI는 정확히 4글자이므로 maxLength={4}는 적절한 제약입니다.


21-31: onChange/onBlur 핸들러 패턴 및 유효성 검사 스키마 검증 완료

코드 검증 결과, 모든 PR 요구사항이 올바르게 구현되었습니다:

  • onChange 검증: mbtiOnChangeSchema로 부분 입력 허용 (1-4글자), 대소문자 구분 없이 각 자리수별 유효성 검사
  • onBlur 검증: mbtiOnBlurSchema로 완전한 4글자 MBTI 검증, 패턴 /^[IEie][SNsn][TFtf][JPjp]$/는 16가지 유효한 조합 모두 포함
  • 대문자 변환: ProfileEditModal 라인 63에서 제출 시 .toUpperCase() 처리
  • 에러 관리: onChange에서 onBlur 에러 제거하여 부분 입력 중 에러 미표시 ✓

검증 패턴이 모든 E/I, S/N, T/F, J/P 조합(총 16가지)을 정확히 지원하므로 추가 수정 불필요합니다.

Comment on lines 86 to 96
} catch {
/*
todo: 이미지 변경과 정보 변경 중 하나라도 실패하면 각 항목에 대한 에러메시지 보여줘야함
이미지 변경과 정보 변경 중 하나라도 실패하면 각 항목에 대한 에러메시지 alert
todo: toast로 변경
*/
console.log('요청 실패', error);
const errors = [];
if (userInfoError) errors.push('사용자 정보');
if (userImageError) errors.push('프로필 이미지');

alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

에러 상태 참조 시점 오류 - 항상 빈 배열이 됨

userInfoErroruserImageError는 컴포넌트 렌더링 시점의 값을 클로저로 캡처합니다. mutateAsync가 실패해도 catch 블록 내에서는 아직 이전 상태를 참조하므로, errors 배열은 항상 빈 상태가 됩니다.

🔎 권장 수정안
      try {
        if (Object.keys(nextProfileInfo).length > 0) {
          await updateUser(nextProfileInfo);
        }
        const imageFileObject = Object.values(profileImage)[0];
        if (imageFileObject) {
          await updateUserImage({ file: imageFileObject });
        }
        close();
-      } catch {
-        const errors = [];
-        if (userInfoError) errors.push('사용자 정보');
-        if (userImageError) errors.push('프로필 이미지');
-
-        alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`);
+      } catch (error) {
+        // mutateAsync는 실패 시 에러를 throw하므로 직접 처리
+        alert('프로필 업데이트에 실패했습니다. 다시 시도해주세요');
      }

또는 각 mutation을 개별적으로 try-catch하여 어떤 작업이 실패했는지 추적:

let userUpdateFailed = false;
let imageUpdateFailed = false;

if (Object.keys(nextProfileInfo).length > 0) {
  try {
    await updateUser(nextProfileInfo);
  } catch {
    userUpdateFailed = true;
  }
}

const imageFileObject = Object.values(profileImage)[0];
if (imageFileObject) {
  try {
    await updateUserImage({ file: imageFileObject });
  } catch {
    imageUpdateFailed = true;
  }
}

if (userUpdateFailed || imageUpdateFailed) {
  const errors = [];
  if (userUpdateFailed) errors.push('사용자 정보');
  if (imageUpdateFailed) errors.push('프로필 이미지');
  alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`);
} else {
  close();
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch {
/*
todo: 이미지 변경과 정보 변경 하나라도 실패하면 항목에 대한 에러메시지 보여줘야함
이미지 변경과 정보 변경 하나라도 실패하면 항목에 대한 에러메시지 alert
todo: toast로 변경
*/
console.log('요청 실패', error);
const errors = [];
if (userInfoError) errors.push('사용자 정보');
if (userImageError) errors.push('프로필 이미지');
alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`);
}
} catch (error) {
// mutateAsync는 실패 시 에러를 throw하므로 직접 처리
alert('프로필 업데이트에 실패했습니다. 다시 시도해주세요');
}
🤖 Prompt for AI Agents
In src/components/pages/user/profile/profile-edit-modal/index.tsx around lines
86 to 96, the catch block reads userInfoError and userImageError which are stale
values captured by the render closure so the errors array is always empty;
instead, track failure locally by wrapping each mutation in its own try/catch
(or set boolean flags immediately when a mutation throws) then build the errors
array from those flags and show the alert (and call close() only when neither
failed).

Comment on lines +113 to +117
<form.Field
validators={{ onChange: profileImageOnChangeSchema }}
children={(field) => <ImageField field={field} />}
name='profileImage'
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ImageFieldinitialImages prop 누락

관련 코드 스니펫(src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx)에서 ImageFieldinitialImages prop을 받도록 정의되어 있습니다. 현재 프로필 이미지 URL을 전달하지 않으면 초기 이미지가 표시되지 않을 수 있습니다.

🔎 권장 수정안
        <form.Field
          validators={{ onChange: profileImageOnChangeSchema }}
-          children={(field) => <ImageField field={field} />}
+          children={(field) => <ImageField field={field} initialImages={[image]} />}
          name='profileImage'
        />

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/pages/user/profile/profile-edit-modal/index.tsx around lines
113 to 117, the ImageField component is rendered without the required
initialImages prop so the current profile image won't be displayed; pass a prop
initialImages that supplies the existing profile image URL (e.g. an array
containing the currentProfileImage or form.initialValues.profileImage) to the
ImageField component (ensure you map undefined/null to an empty array) so the
field shows the initial image.

Comment on lines +3 to +7
export const profileImageOnChangeSchema = z.union([
z.object({ blobUrl: z.instanceof(File) }),
z.object({ Url: z.null() }),
z.object({}),
]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for ImageRecord type definition
echo "=== ImageRecord 타입 정의 ==="
rg -n "type ImageRecord|interface ImageRecord" --type ts -A 2

echo -e "\n=== profileImageOnChangeSchema 사용처 ==="
rg -n "profileImageOnChangeSchema" --type ts -A 3

echo -e "\n=== profileImageOnChangeSchema 정의 주변 전체 내용 ==="
cat -n src/lib/schema/mypage.ts | head -40

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 2854


🏁 Script executed:

#!/bin/bash
# Find and examine ImageField component
echo "=== ImageField 컴포넌트 정의 찾기 ==="
rg -n "export.*ImageField|function ImageField" --type tsx -A 20

echo -e "\n=== imageinput/index.tsx 전체 내용 ==="
cat -n src/components/ui/imageinput/index.tsx

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 4769


🏁 Script executed:

#!/bin/bash
# Search for ImageField definition
echo "=== ImageField 컴포넌트 찾기 ==="
rg -n "ImageField|export.*ImageField" --type ts -B 2 -A 15 | head -100

echo -e "\n=== profile-edit-modal 구조 ==="
ls -la src/components/pages/user/profile/profile-edit-modal/

echo -e "\n=== field 관련 코드 확인 ==="
rg -n "class.*Field|export.*Field" src/components/pages/user/profile/profile-edit-modal/ --type ts -A 5

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 6433


🏁 Script executed:

#!/bin/bash
# Get full ImageField implementation
echo "=== ImageField 전체 코드 ==="
cat -n src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx

echo -e "\n=== form.Field 사용 방식 확인 ==="
rg -n "form\.Field" src/components/pages/user/profile/profile-edit-modal/index.tsx -B 2 -A 5 | head -60

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 3337


🏁 Script executed:

#!/bin/bash
# Final confirmation: Check what the validator actually receives
echo "=== Confirm flow: ImageInput onChange -> field.handleChange -> validator ==="
echo "ImageInput.onChange signature:"
rg -n "onChange\?" src/components/ui/imageinput/index.tsx | grep -A2 "ImageInputProps"

echo -e "\n=== Confirm ImageRecord structure with blob URLs ==="
rg -n "URL.createObjectURL" src/components/ui/imageinput/index.tsx -B 2 -A 2

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 390


profileImageOnChangeSchema의 스키마 구조가 실제 데이터 구조와 완전히 불일치하여 검증 실패

ImageInput 컴포넌트가 field.handleChange에 전달하는 데이터는 Record<string, File | null> 형태이며, 키는 URL.createObjectURL()로 생성된 동적인 blob URL입니다 (예: { "blob:http://...": File }).

그러나 현재 스키마는 고정된 키 이름(blobUrl, Url)을 기대하고 있으므로 실제 데이터와 일치하지 않아 모든 검증이 실패합니다. 스키마를 z.record(z.string(), z.union([z.instanceof(File), z.null()])) 형태로 수정하여 동적 키를 허용하도록 변경해야 합니다.

🤖 Prompt for AI Agents
In src/lib/schema/mypage.ts around lines 3 to 7, the profileImageOnChangeSchema
expects fixed property names but the component sends a Record with dynamic
blob-URL keys; replace the current union of fixed objects with a record schema
that allows any string key and values that are either File instances or null
(e.g., z.record(z.string(), z.union([z.instanceof(File), z.null()]))) so
validation matches the actual Record<string, File | null> shape.

Comment on lines 13 to 20
// 2. Content Type 검증
if (!IMAGE_CONFIG.allowedTypes.includes(file.type)) {
const currentFileType = file.type.split('/')[1];
return {
valid: false,
error: `${IMAGE_CONFIG.allowedExtensions.join(', ')} 형식만 업로드 가능합니다. \n(현재: ${currentFileType})`,
};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

file.type이 빈 문자열일 경우 에러 메시지가 불명확해질 수 있습니다.

브라우저가 파일 타입을 결정하지 못하면 file.type이 빈 문자열("")이 될 수 있습니다. 이 경우 file.type.split('/')[1]undefined를 반환하여 에러 메시지에 "(현재: undefined)"가 표시됩니다.

🔎 fallback 처리 제안
  // 2. Content Type 검증
  if (!IMAGE_CONFIG.allowedTypes.includes(file.type)) {
-   const currentFileType = file.type.split('/')[1];
+   const currentFileType = file.type ? file.type.split('/')[1] : '알 수 없음';
    return {
      valid: false,
      error: `${IMAGE_CONFIG.allowedExtensions.join(', ')} 형식만 업로드 가능합니다. \n(현재: ${currentFileType})`,
    };
  }
🤖 Prompt for AI Agents
In src/lib/validateImage.ts around lines 13 to 20, file.type can be an empty
string so file.type.split('/')[1] may be undefined and produce "(현재:
undefined)"; update the code to compute a safe currentFileType by first checking
if file.type is a non-empty string containing a slash and using the part after
the slash, otherwise try to fall back to the file extension from file.name (e.g.
last segment after '.') if present, and if neither is available use a clear
placeholder like "알 수 없음" — then use that safe value in the returned error
message.

@Chiman2937
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/pages/user/profile/profile-edit-modal/index.tsx (1)

86-87: 에러 처리가 단순화되었지만, 선택적으로 개선 가능합니다.

통합 에러 메시지로 단순화하여 이전 리뷰에서 지적된 stale closure 문제를 해결했습니다. 다만 어떤 업데이트(사용자 정보 vs 프로필 이미지)가 실패했는지에 대한 정보는 손실되었습니다.

사용자에게 더 명확한 피드백을 제공하려면 각 mutation을 개별 try-catch로 감싸서 실패한 항목을 추적할 수 있습니다.

🔎 선택적 개선안: 세밀한 에러 추적
      try {
+       let userUpdateFailed = false;
+       let imageUpdateFailed = false;
+
        if (Object.keys(nextProfileInfo).length > 0) {
-         await updateUser(nextProfileInfo);
+         try {
+           await updateUser(nextProfileInfo);
+         } catch {
+           userUpdateFailed = true;
+         }
        }
        const imageFileObject = Object.values(profileImage)[0];
        if (imageFileObject) {
-         await updateUserImage({ file: imageFileObject });
+         try {
+           await updateUserImage({ file: imageFileObject });
+         } catch {
+           imageUpdateFailed = true;
+         }
        }
-       close();
-     } catch {
-       alert(`업데이트에 실패했습니다. 잠시 후 다시 시도해주세요`);
+
+       if (userUpdateFailed || imageUpdateFailed) {
+         const errors = [];
+         if (userUpdateFailed) errors.push('사용자 정보');
+         if (imageUpdateFailed) errors.push('프로필 이미지');
+         alert(`${errors.join(', ')} 업데이트에 실패했습니다. 다시 시도해주세요`);
+       } else {
+         close();
+       }
+     } catch (error) {
+       // 예상치 못한 에러 처리
+       alert(`업데이트에 실패했습니다. 잠시 후 다시 시도해주세요`);
      }
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba30ecc and 88bfa27.

📒 Files selected for processing (7)
  • src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx (0 hunks)
  • src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx (1 hunks)
  • src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx (1 hunks)
  • src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx (1 hunks)
  • src/components/pages/user/profile/profile-edit-modal/index.tsx (6 hunks)
  • src/components/ui/imageinput/index.tsx (3 hunks)
  • src/lib/validateImage.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • src/components/pages/user/profile/profile-edit-fields/image-field/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/pages/user/profile/profile-edit-fields/nickname-field/index.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-17T09:40:37.733Z
Learnt from: HopeFullee
Repo: WeGo-Together/WeGo_FrontEnd PR: 205
File: src/components/pages/post-meetup/fields/cap-field/index.tsx:47-47
Timestamp: 2025-12-17T09:40:37.733Z
Learning: In TanStack Form with Zod validation, when field.state.meta.isValid is false, field.state.meta.errors is guaranteed to have at least one entry. Therefore, in an isInvalid check defined as field.state.meta.isTouched && !field.state.meta.isValid, safely access field.state.meta.errors[0] without extra null/empty checks. This guideline applies to TSX form fields under src/components/pages (and similar forms using the same pattern).

Applied to files:

  • src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx
  • src/components/pages/user/profile/profile-edit-modal/index.tsx
  • src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx
🧬 Code graph analysis (3)
src/components/pages/user/profile/profile-edit-modal/index.tsx (5)
src/hooks/use-user/use-user-update/index.ts (1)
  • useUpdateUser (7-17)
src/hooks/use-user/use-user-image-update/index.ts (1)
  • useUserImageUpdate (7-17)
src/api/index.ts (1)
  • API (22-22)
src/types/service/user.ts (1)
  • UpdateMyInfoPayloads (21-25)
src/lib/schema/mypage.ts (5)
  • profileImageOnChangeSchema (3-7)
  • nickNameOnChangeSchema (9-12)
  • profileMessageOnChangeSchema (14-16)
  • mbtiOnChangeSchema (18-28)
  • mbtiOnBlurSchema (30-36)
src/lib/validateImage.ts (1)
src/lib/constants/image.ts (1)
  • IMAGE_CONFIG (1-7)
src/components/ui/imageinput/index.tsx (2)
src/lib/constants/image.ts (1)
  • IMAGE_CONFIG (1-7)
src/lib/validateImage.ts (1)
  • validateImage (3-34)
🪛 Biome (2.1.2)
src/components/pages/user/profile/profile-edit-modal/index.tsx

[error] 115-116: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)


[error] 120-120: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)


[error] 124-125: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)


[error] 129-129: Avoid passing children using a prop

The canonical way to pass children in React is to use JSX elements

(lint/correctness/noChildrenProp)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: preview-deploy
  • GitHub Check: chromatic
  • GitHub Check: coverage
🔇 Additional comments (6)
src/components/ui/imageinput/index.tsx (1)

107-123: 이미지 검증 로직이 올바르게 구현되었습니다.

각 파일에 대해 validateImage를 호출하여 확장자, 크기, 치수를 검증하고, 검증 실패 시 입력을 초기화하여 잘못된 파일이 추가되지 않도록 방지하는 로직이 잘 구현되었습니다.

alert 사용은 코멘트에서 언급된 대로 임시 방편으로 보이며, 향후 toast 알림으로 교체 시 더 나은 사용자 경험을 제공할 수 있습니다.

src/components/pages/user/profile/profile-edit-fields/message-field/index.tsx (1)

10-23: 이전 리뷰의 접근성 문제가 해결되었습니다.

LabelhtmlForInputid'profile-message'로 올바르게 연결되었으며, 유효하지 않은 필드에 대한 Hint 표시도 적절하게 구현되었습니다.

src/components/pages/user/profile/profile-edit-fields/mbti-field/index.tsx (1)

10-34: 이전 리뷰의 접근성 문제가 해결되었으며, MBTI 검증 로직이 적절합니다.

LabelhtmlForInputid가 올바르게 연결되었고, maxLength={4} 제약과 onBlur 검증이 추가되었습니다.

onChange 핸들러에서 errorMap.onBlur를 수동으로 제거하는 부분(lines 25-31)은 사용자가 입력 중일 때 onBlur 에러 메시지를 즉시 제거하여 더 나은 UX를 제공하기 위한 의도적인 구현으로 보입니다. 이는 PR 목표에 명시된 요구사항(onChange 시 부분 MBTI 허용, onBlur 시 완전한 MBTI 요구)과 일치합니다.

src/components/pages/user/profile/profile-edit-modal/index.tsx (3)

45-59: 닉네임 중복 확인 로직이 잘 구현되었습니다.

onSubmitAsync 검증자에서 닉네임이 변경된 경우에만 API를 호출하고(line 47), 사용 불가능한 닉네임에 대해 필드 수준의 에러를 반환하여 사용자에게 명확한 피드백을 제공합니다.


105-124: Biome 경고는 무시해도 됩니다 - TanStack Form의 정상적인 API 사용입니다.

Biome가 children prop 사용에 대해 경고하고 있지만, 이는 TanStack Form의 form.Field 컴포넌트가 render function을 받는 공식 API입니다. JSX children이 아닌 함수형 children 패턴으로, 필드 상태를 하위 컴포넌트에 전달하기 위한 의도적인 설계입니다.

각 필드에 적절한 검증 스키마(onChange, onBlur)가 적용되어 있고, 필드별 에러 표시도 잘 구현되었습니다.


129-135: 동적 제출 버튼 구현이 우수합니다.

form.Subscribe를 사용하여 canSubmitisSubmitting 상태에 따라 버튼의 활성화 여부와 레이블을 동적으로 변경하는 것은 좋은 사용자 경험을 제공합니다.

};
}

// 3. 파일 사이즈 검증
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

주석이 검증 내용과 일치하지 않습니다.

Line 24의 주석이 "// 3. 파일 사이즈 검증"으로 되어 있지만, 실제로는 이미지의 너비와 높이(치수)를 검증하고 있습니다. 파일 크기 검증은 이미 lines 15-22에서 수행되었습니다.

🔎 주석 수정 제안
-  // 3. 파일 사이즈 검증
+  // 3. 이미지 치수 검증
   const { width, height } = await getImageDimensions(file);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 3. 파일 사이즈 검증
// 3. 이미지 치수 검증
const { width, height } = await getImageDimensions(file);
🤖 Prompt for AI Agents
In src/lib/validateImage.ts around line 24, the comment "// 3. 파일 사이즈 검증" is
incorrect because the code validates image dimensions (width/height), not file
size; update the comment to accurately reflect the check (for example "// 3. 이미지
치수 검증 (너비/높이)") and ensure numbering and language match surrounding comments.

Copy link
Contributor

@wooktori wooktori left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다!

@Chiman2937 Chiman2937 merged commit 6f85ec7 into main Dec 22, 2025
9 checks passed
@Chiman2937 Chiman2937 deleted the chiyoung-feat/mypage-validation branch December 22, 2025 00:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready For Review! 리뷰 받을 준비가 되었습니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[마이 페이지] 프로필 편집 모달 유효성검사 적용

3 participants